/*
 *  Copyright 2011 Alexey Andreev.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package org.xthl.core;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import org.nop.core.*;
import org.nop.sql.DataManager;
import org.nop.templating.Template;
import org.nop.templating.TemplateFactory;
import org.nop.templating.TemplatingContext;
import org.nop.templating.XmlTreeBuilder;
import org.nop.util.ContextBeanExtender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xthl.core.api.Block;
import org.xthl.core.api.BlockLogic;
import org.xthl.core.api.CustomBlock;
import org.xthl.core.api.Requires;
import org.xthl.core.api.SpecialBlock;
import org.xthl.core.api.UserLogic;
import org.xthl.core.view.BlockSight;
import org.xthl.core.view.NotFoundPageView;

/**
 *
 * @author Alexey Andreev
 */
public class CoreModule extends AbstractModule {
    private static final Logger logger = LoggerFactory.getLogger(CoreModule.class);
    
    @Override
    protected void load() {
        app.register(UsersFeature.class, new UsersFeatureImpl(app));
        app.registerPackageProcessor(new UsersPackageProcessor());
        MenuFeature menuFeature = new MenuFeatureImpl(app);
        app.register(MenuFeature.class, menuFeature);
        app.registerPackageProcessor(new CorePackageProcessor());
        BlocksFeature blocksFeature = new BlocksFeatureImpl(app);
        app.register(BlocksFeature.class, blocksFeature);
        app.register(FragmentsFeature.class, new FragmentsFeatureImpl(app));
        app.registerClass(BlockConsumer.class, BlockSet.class);
        app.registerRequest(BlockProvider.class, new RequestBeanCreator<BlockProvider>() {
            @Override
            public BlockProvider createBean(Request context) {
                return (BlockProvider)context.get(BlockConsumer.class);
            }
        });
        app.get(ControllersFeature.class).addDispatcherHandler(new DispatcherHandler() {
            @Override
            public void beforeRequest(Request request) {
            }
            
            @Override
            public void afterRequest(DispatcherRequestResult request) {
                if (request.getContent() == null) {
                    TemplatingContext context = request.get(TemplatingContext.class);
                    NotFoundPageView view = request.get(TemplateFactory.class)
                            .create(NotFoundPageView.class, context);
                    request.setContent(Content.html(view
                            .setAddress(request.getRequestPath())));
                    request.getContent().setHttpCode(404);
                }
            }
        });
        app.loadPackage("org.xthl.core");
        app.addRequestHandler(new UsersRequestHandler());
        app.addRequestHandler(new RequestHandler() {
            @Override
            public void beforeRequest(RequestBuilder request) {
                prepareBlocks(request);
            }
            
            @Override
            public void afterRequest(RequestResult request, Throwable exception) {
            }
        });
        app.register(StylingFeature.class, new StylingFeatureImpl(app));
        menuFeature.registerItem(UserListMenuItem.class);
        menuFeature.registerItem(RoleListMenuItem.class);
        menuFeature.registerItem(BlocksMenuItem.class);
    }
    
    private void prepareBlocks(Request request) {
        BlockLogic logic = request.get(BlockLogic.class);
        BlockCache cache = app.get(BlockCache.class);
        BlockConsumer consumer = request.get(BlockConsumer.class);
        for (Block block : logic.getEnabledBlocks()) {
            if (block instanceof SpecialBlock) {
                BlockSight sight = new BlockSight();
                SpecialBlock special = (SpecialBlock)block;
                BlockRenderer renderer = (BlockRenderer)request.create(special.getRendererType());
                if (!renderer.isVisible()) {
                    continue;
                }
                sight.setTitle(renderer.getTitle());
                sight.setView(renderer.getView());
                consumer.addBlock(sight);
            } else if (block instanceof CustomBlock) {
                BlockSight sight = new BlockSight();
                final CustomBlock custom = (CustomBlock)block;
                sight.setTitle(new Template() {
                    @Override
                    public void generate(XmlTreeBuilder treeBuilder) {
                        treeBuilder.addText(custom.getTitle());
                    }
                });
                sight.setView(cache.getCustomBlockContent(custom, logic));
                consumer.addBlock(sight);
            }
        }
    }
    
    private Map<Method, String[]> getRestrictedMethods(Class<?> beanType) {
        final Map<Method, String[]> restrictedMethods = new HashMap<Method, String[]>();
        for (Method method : beanType.getMethods()) {
            Requires requires = method.getAnnotation(Requires.class);
            if (requires != null) {
                restrictedMethods.put(method, requires.value());
            }
        }
        return restrictedMethods;
    }
    
    private void readConfig() {
        app.register(UsersConfigService.class, app.get(Configurator.class)
                .readConfig(UsersConfig.class));
    }
    
    private void extendBeansWithSecurity() {
        for (final Class<?> beanType : app.getRegisteredRemoteBeanTypes()) {
            final Map<Method, String[]> restrictedMethods = getRestrictedMethods(beanType);
            @SuppressWarnings("unchecked")
            Class<Object> safeBeanType = (Class<Object>)beanType;
            if (!restrictedMethods.isEmpty()) {
                if (logger.isInfoEnabled()) {
                    logger.info("Extending remote bean " + safeBeanType.getName() +
                            " with security checks");
                }
                app.extendRemoteCall(safeBeanType, new ContextBeanExtender<Object, RemoteCall>() {
                    @Override
                    public Object extend(final RemoteCall context, final Object bean) {
                        ClassLoader cl = Thread.currentThread().getContextClassLoader();
                        return Proxy.newProxyInstance(cl, new Class<?>[] { beanType },
                                new InvocationHandler() {
                            @Override
                            public Object invoke(Object proxy, Method method,
                                    Object[] args) throws Throwable {
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Checking permissions for method " + method);
                                }
                                String[] permissions = restrictedMethods.get(method);
                                if (permissions != null) {
                                    UserLogic logic = new UserLogicImpl(
                                            context.get(DataManager.class),
                                            context.get(UserSession.class),
                                            context.get(UsersConfigService.class),
                                            context.get(PermissionChecker.class));
                                    for (String permission : permissions) {
                                        logic.requirePermission(permission);
                                    }
                                }
                                try {
                                    return method.invoke(bean, args);
                                } catch (InvocationTargetException ex) {
                                    throw ex.getTargetException();
                                }
                            }
                        });
                    }
                });
            }
        }
        for (final Class<?> beanType : app.getRegisteredRequestBeanTypes()) {
            final Map<Method, String[]> restrictedMethods = getRestrictedMethods(beanType);
            @SuppressWarnings("unchecked")
            Class<Object> safeBeanType = (Class<Object>)beanType;
            if (!restrictedMethods.isEmpty()) {
                if (logger.isInfoEnabled()) {
                    logger.info("Extending request bean " + safeBeanType.getName() +
                            " with security checks");
                }
                app.extendRequest(safeBeanType, new RequestBeanExtender<Object>() {
                    @Override
                    public Object extend(final Request context, final Object bean) {
                        ClassLoader cl = Thread.currentThread().getContextClassLoader();
                        return Proxy.newProxyInstance(cl, new Class<?>[] { beanType },
                                new InvocationHandler() {
                            @Override
                            public Object invoke(Object proxy, Method method,
                                    Object[] args) throws Throwable {
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Checking permissions for method " + method);
                                }
                                String[] permissions = restrictedMethods.get(method);
                                if (permissions != null) {
                                    UserLogic logic = new UserLogicImpl(
                                            context.get(DataManager.class),
                                            context.get(UserSession.class),
                                            context.get(UsersConfigService.class),
                                            context.get(PermissionChecker.class));
                                    for (String permission : permissions) {
                                        logic.requirePermission(permission);
                                    }
                                }
                                try {
                                    return method.invoke(bean, args);
                                } catch (InvocationTargetException ex) {
                                    throw ex.getTargetException();
                                }
                            }
                        });
                    }
                });
            }
        }
    }
    
    @Override
    public void start() {
        super.start();
        readConfig();
        extendBeansWithSecurity();
    }
}
